/*
 * Copyright (C) 2013 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { List } from "@kit.ArkTS";
import { IllegalArgumentException } from "./Error/error";
import { Charset } from "./utils/Charset";
import { kotlinRequire } from "./utils/Utils";

/**
 * An [RFC 2045][rfc_2045] Media Type, appropriate to describe the content type of an HTTP request
 * or response body.
 *
 * [rfc_2045]: http://tools.ietf.org/html/rfc2045
 */
export class MediaType {
  // TOKEN = "([a-zA-Z0-9-!#$%&'*+.^_`{|}~]+)"
  private static readonly TOKEN = "([a-zA-Z0-9!#$%&'*+\\-.^_`{|}~]+)"
  // QUOTED = \"([^\"]*)\"  
  private static readonly QUOTED = '"([^"]*)"'
  // TYPE_SUBTYPE = Pattern.compile("$TOKEN/$TOKEN")
  private static readonly TYPE_SUBTYPE = new RegExp(`^${MediaType.TOKEN}/${MediaType.TOKEN}`, 'i')
  // PARAMETER = Pattern.compile(";\\s*(?:$TOKEN=(?:$TOKEN|$QUOTED))?")
  private static readonly PARAMETER = new RegExp(
    `;\\s*(?:${MediaType.TOKEN}=(?:${MediaType.TOKEN}|${MediaType.QUOTED}))?`,
    'g' // 使用全局标志以匹配多个参数
  )

  private mediaType: string

  /**
   * Returns the high-level media type, such as "text", "image", "audio", "video", or "application".
   */
  // @get:JvmName("type") val type: string
  #type: string
  get type(): string {
    return this.#type
  }

  /**
   * Returns a specific media subtype, such as "plain" or "png", "mpeg", "mp4" or "xml".
   */
  // @get:JvmName("subtype") val subtype: string
  #subtype: string

  get subtype(): string {
    return this.#subtype
  }

  /** Alternating parameter names with their values, like `["charset", "utf-8"]`. */
  private parameterNamesAndValues: Array<string>

  constructor(mediaType: string, type: string, subtype: string, parameterNamesAndValues: Array<string>) {
    this.mediaType = mediaType
    this.#type = type
    this.#subtype = subtype
    this.parameterNamesAndValues = parameterNamesAndValues
  }


  /**
   * Returns the charset of this media type, or [defaultValue] if either this media type doesn't
   * specify a charset, of it its charset is unsupported by the current runtime.
   */
  // @JvmOverloads
  charset(defaultValue: Charset | null = null): Charset | null {
    const charset = this.parameter("charset")
    if (charset === null) return defaultValue
    try {
      return Charset.forName(charset)
    } catch (_) {
      return defaultValue // This charset is invalid or unsupported. Give up.
    }
  }

  /**
   * Returns the parameter [name] of this media type, or null if this media type does not define
   * such a parameter.
   */
  parameter(name: string): string | null {
    const nameLower = name.toLowerCase()
    for (let i = 0; i < this.parameterNamesAndValues.length; i += 2) {
      if (this.parameterNamesAndValues[i].toLowerCase() === nameLower) {
        return this.parameterNamesAndValues[i + 1]
      }
    }
    return null
  }

  /**
   * Returns the encoded media type, like "text/plain; charset=utf-8", appropriate for use in a
   * Content-Type header.
   */
  toString(): string {
    return this.mediaType
  }

  equals(other?: unknown): boolean {
    return other instanceof MediaType && other.mediaType === this.mediaType
  }

  hashCode(): void {
    // return this.mediaType.hashCode()
  }

  /**
   * Returns a media type for this string.
   *
   * @throws IllegalArgumentException if this is not a well-formed media type.
   */
  // @JvmStatic
  // @JvmName("get")
  static toMediaType(str: string): MediaType {
    const typeSubtypeMatch = MediaType.TYPE_SUBTYPE.exec(str)
    if (!typeSubtypeMatch) {
      throw new IllegalArgumentException(`No subtype found for: "${str}"`)
    }
    // 获取 type 和 subtype 并转小写
    const type = typeSubtypeMatch[1].toLowerCase()      // 等价于 group(1).toLowerCase(Locale.US)
    const subtype = typeSubtypeMatch[2].toLowerCase()  // 等价于 group(2).toLowerCase(Locale.US)

    const parameterNamesAndValues = new List<string>()
    let s = typeSubtypeMatch[0].length

    this.PARAMETER.lastIndex = s
    while (s < str.length) {
      this.PARAMETER.lastIndex = s
      const paramMatch = this.PARAMETER.exec(str)
      // 必须匹配到一个参数（从当前位置）
      if (!paramMatch || paramMatch.index !== s) {
        throw new IllegalArgumentException(`Parameter is not formatted correctly: "${str.substring(s)}" for: "${str}"`)
      }
      const name = paramMatch[1]?.toLowerCase() // 参数名转小写
      if (!name) {
        s = this.PARAMETER.lastIndex
        continue
      }
      let value: string | undefined

      const tokenValue = paramMatch[2] // 非引号值
      const quotedValue = paramMatch[3] // 引号内值
      if (tokenValue !== undefined) {
        // 检查是否是单引号包裹（非法，但宽松处理）
        if (tokenValue.startsWith("'") && tokenValue.endsWith("'") && tokenValue.length > 2) {
          value = tokenValue.substring(1, tokenValue.length - 1) // 去除单引号
        } else {
          value = tokenValue
        }
      } else if (quotedValue !== undefined) {
        // 双引号值，group(3) 已自动去除引号
        value = quotedValue
      }
      // else: value 保持 undefined（如 ;charset= 后面为空）

      parameterNamesAndValues.add(name)
      parameterNamesAndValues.add(value)
      s = this.PARAMETER.lastIndex
    }

    return new MediaType(str, type, subtype, parameterNamesAndValues.convertToArray())
  }

  /** Returns a media type for this, or null if this is not a well-formed media type. */
  // @JvmStatic
  // @JvmName("parse")
  static toMediaTypeOrNull(str: string): MediaType | null {
    try {
      return MediaType.toMediaType(str)
    } catch (_) {
      return null
    }
  }
}
